import { Client, StompSubscription } from '@stomp/stompjs';
import { Module } from 'vuex';
import { State } from '@/store';
import { ObjectUtil } from '@/utils/ObjectUtil';
import { messageCallbackType } from '@stomp/stompjs/esm6/types';
import {
  ADD_SUBSCRIPTION,
  client,
  connect,
  DEL_SUBSCRIPTION,
  disconnect,
  isConnect,
  RESET_STATE,
  SET_CLIENT,
  subscribe,
  unsubscribe,
} from '@/store/modules/stomp/pool';
import { DataObject } from '@/utils/types';
import { router } from '@/router';

export interface StompState {
  client: Client | null;
  subscriptions: DataObject<StompSubscription>;
}

export const stomp: Module<StompState, State> = {
  namespaced: true,
  state: () => ({
    client: null,
    subscriptions: {},
  }),
  getters: {
    [client]: state => state.client,
    [isConnect]: state => state.client !== null,
  },
  mutations: {
    [RESET_STATE]: state => {
      Object.assign(state, {
        client: null,
        subscriptions: {},
      });
    },
    [SET_CLIENT]: (state, client) => {
      state.client = client;
    },
    [ADD_SUBSCRIPTION]: (
      state,
      { destination, subscription }: { destination: string; subscription: StompSubscription }
    ) => {
      state.subscriptions = {
        ...state.subscriptions,
        [destination]: subscription,
      };
    },
    [DEL_SUBSCRIPTION]: (state, destination) => {
      const subscriptions = {
        ...state.subscriptions,
      };
      delete subscriptions[destination];
      state.subscriptions = subscriptions;
    },
  },
  actions: {
    [connect]({ commit, dispatch }, headers) {
      return new Promise<void>((resolve, reject) => {
        const client = new Client({
          brokerURL: process.env.VUE_APP_WEBSOCKET,
          connectHeaders: headers,
          onConnect: () => {
            commit(SET_CLIENT, client);
            resolve();
          },
          onDisconnect: () => {
            commit(RESET_STATE);
          },
          onStompError: () => {
            dispatch('account/logout', false, { root: true })
              .then(() => router.push('/login'))
              .then(reject);
          },
          onWebSocketError: () => {
            dispatch('account/logout', false, { root: true })
              .then(() => router.push('/login'))
              .then(reject);
          },
        });
        client.activate();
      });
    },
    [disconnect]({ commit, state }) {
      const client = state.client;
      if (client) {
        commit(RESET_STATE);
        return client.deactivate();
      }
    },
    [subscribe]({ commit, state }, { destination, callback }: { destination: string; callback: messageCallbackType }) {
      if (state.client) {
        const subscription = state.client.subscribe(destination, callback);
        commit(ADD_SUBSCRIPTION, { destination, subscription });
      } else {
        throw new Error('未建立WebSocket连接');
      }
    },
    [unsubscribe]({ commit, state }, destination: string) {
      if (state.client && ObjectUtil.has(state.subscriptions, destination)) {
        state.subscriptions[destination].unsubscribe();
        commit(DEL_SUBSCRIPTION, destination);
      } else if (!state.client) {
        throw new Error('未建立WebSocket连接');
      } else {
        throw new Error(`未订阅 ${destination}`);
      }
    },
  },
};
